package com.swak.router;

import java.util.Optional;
import java.util.function.Function;

import org.springframework.util.Assert;

import com.swak.router.pattern.PathContainer;
import com.swak.router.pattern.PathPattern;
import com.swak.router.pattern.PathPatternParser;

public abstract class RequestPredicates {

	private static final PathPatternParser DEFAULT_PATTERN_PARSER = new PathPatternParser();

	/**
	 * Return a {@code RequestPredicate} that always matches.
	 * 
	 * @return a predicate that always matches
	 */
	public static RequestPredicate all() {
		return request -> true;
	}

	/**
	 * Return a {@code RequestPredicate} that tests the request path against the
	 * given path pattern.
	 * 
	 * @param pattern the pattern to match to
	 * @return a predicate that tests against the given path pattern
	 */
	public static RequestPredicate path(String pattern) {
		Assert.notNull(pattern, "'pattern' must not be null");
		return patternPredicates(DEFAULT_PATTERN_PARSER).apply(pattern);
	}

	/**
	 * Return a function that creates new path-matching {@code RequestPredicates}
	 * from pattern Strings using the given {@link PathPatternParser}. This method
	 * can be used to specify a non-default, customized {@code PathPatternParser}
	 * when resolving path patterns.
	 * 
	 * @param patternParser the parser used to parse patterns given to the returned
	 *                      function
	 * @return a function that resolves patterns Strings into path-matching
	 *         {@code RequestPredicate}s
	 */
	public static Function<String, RequestPredicate> patternPredicates(PathPatternParser patternParser) {
		Assert.notNull(patternParser, "'patternParser' must not be null");
		return pattern -> new PathPatternPredicate(patternParser.parse(pattern));
	}

	/**
	 * path 处理
	 * 
	 * @author lifeng
	 */
	public static class PathPatternPredicate implements RequestPredicate {

		private final PathPattern pattern;

		public PathPatternPredicate(PathPattern pattern) {
			this.pattern = pattern;
		}

		@Override
		public boolean test(Route request) {
			PathContainer pathContainer = request.getRequestPath();
			PathPattern.PathMatchInfo info = this.pattern.matchAndExtract(pathContainer);
			if (info != null) {
				return true;
			} else {
				return false;
			}
		}

		@Override
		public String toString() {
			return String.format("(pattern %s)", this.pattern.getPatternString());
		}
	}

	public static class AndRequestPredicate implements RequestPredicate {

		private final RequestPredicate left;

		private final RequestPredicate right;

		public AndRequestPredicate(RequestPredicate left, RequestPredicate right) {
			this.left = left;
			this.right = right;
		}

		@Override
		public boolean test(Route t) {
			return this.left.test(t) && this.right.test(t);
		}

		@Override
		public Optional<Route> nest(Route request) {
			return this.left.nest(request).flatMap(this.right::nest);
		}

		@Override
		public String toString() {
			return String.format("(%s && %s)", this.left, this.right);
		}
	}

	public static class OrRequestPredicate implements RequestPredicate {

		private final RequestPredicate left;

		private final RequestPredicate right;

		public OrRequestPredicate(RequestPredicate left, RequestPredicate right) {
			this.left = left;
			this.right = right;
		}

		@Override
		public boolean test(Route t) {
			if (this.left.test(t)) {
				return true;
			} else {
				if (this.right.test(t)) {
					return true;
				}
			}
			return false;
		}

		@Override
		public Optional<Route> nest(Route request) {
			Optional<Route> leftResult = this.left.nest(request);
			if (leftResult.isPresent()) {
				return leftResult;
			} else {
				return this.right.nest(request);
			}
		}

		@Override
		public String toString() {
			return String.format("(%s || %s)", this.left, this.right);
		}
	}

	public static class NegateRequestPredicate implements RequestPredicate {

		private final RequestPredicate delegate;

		public NegateRequestPredicate(RequestPredicate delegate) {
			this.delegate = delegate;
		}

		@Override
		public boolean test(Route t) {
			return !this.delegate.test(t);
		}

		@Override
		public String toString() {
			return "!" + this.delegate.toString();
		}
	}
}